<?php
###########################################################################
#  Copyright (C) 2006-2007 Glyphix, Inc. <briz@glyphix.com>               #
#                                                                         #
#  Permission is hereby granted, free of charge, to any person obtaining  #
#  a copy of this software and associated documentation files (the        #
#  "Software"), to deal in the Software without restriction, including    #
#  without limitation the rights to use, copy, modify, merge, publish,    #
#  distribute, sublicense, and/or sell copies of the Software, and to     #
#  permit persons to whom the Software is furnished to do so, subject to  #
#  the following conditions:                                              #
#                                                                         #
#  The above copyright notice and this permission notice shall be         #
#  included in all copies or substantial portions of the Software.        #
#                                                                         #
#  Except as contained in this notice, the name(s) of the above           #
#  copyright holders shall not be used in advertising or otherwise to     #
#  promote the sale, use or other dealings in this Software without       #
#  prior written authorization.                                           #
#                                                                         #
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        #
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     #
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
#  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR      #
#  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  #
#  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
#  OTHER DEALINGS IN THE SOFTWARE.                                        #
###########################################################################

/**
 * GXPage_Page
 *
 * Loads the XML for a page as defined in Site.xml and processes it.
 * This requires two steps: load the page itself, then process it for includes.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	Engine
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 * @uses		GXPage_SubClass
 */

class GXPage_Page extends GXPage_SubClass {

	// the id of the page
	public $id = null;

	// the processed xml for the xml file itself ... available after ::load() runs
	public $file = null;

	public function __construct( $id = null ){
		if( !$id ){
			trigger_error( 'Missing id', E_USER_ERROR );
			return false;
		}
		parent::__construct();

		// find the <Page> node
		if( !($PageConf = GXPage::get('SiteConfig')->getPage($id)) ){
			trigger_error( 'Unable to get Page ID', E_USER_ERROR );
			return false;
		}
		$this->id = $PageConf->attributes['ID'];

		// store the <Page> node as this object's config
		$this->setConfig($PageConf);
		return $this;
	}

	/**
	 * getPage
	 *
     * Returns the page GXDOM object.
	 * @return object GXDOM
     */
	public function getPage(){
		return $this->result;
	}

	/**
	 * getID
	 *
     * Returns the ID of this page.
	 * @return string
     */
	public function getID(){
		return $this->id;
	}

	/**
	 * load
	 *
     * Loads the requested XML page and looks for @_Page and @_Action.
	 * It then replaces each node with @_Page and @_Action with the results of calling Page::Action.
	 *
	 * @file the xml file name (if supplied, overrides <File> specified in Site.xml)
	 * @return object GXDOM
     */
	public function load( $fileName = null ){
		if( !$this->getConfig() ){
			trigger_error( 'Missing Page', E_USER_ERROR );
			return false;
		}
		if( !$fileName ){
			// get the path to the xml file
			if( !($fileName = $this->getConfig()->exec->xpath('File',0)->nodeValue) ){
				trigger_error( 'Unable to get a value for File for ' .$this->id, E_USER_ERROR );
				return false;
			}
		}

		trigger_error( 'Loading ' .$fileName, E_USER_WARNING );

		// build the file path
		$file = GXPage::get('Layout')->FS->Pages .$fileName;
		if( !is_file($file) ){
			trigger_error( 'No file by that name (' .$file .')', E_USER_ERROR );
			return false;
		}
		trigger_error( 'Using ' .$file .' as path to the base xml file', E_USER_WARNING );

		// load XML file
		$xml = new GXDOM($file);
		if( !$xml ){
			trigger_error( $file .' is not well-formed', E_USER_NOTICE );
			return false;
		}

		// run an xpath to locate all nodes to be replaced
		// get all nodes that have both _Page and _Action attributes, no matter the depth
		if( $replace = $xml->exec->xpath('//*[ @_Page ]') ){
			// loop thru each hit, replacing with results of method calls
			foreach( $replace as $existing ){
				// assign object to Obj var
				$Page 	= $existing->attributes['_Page'];
				$Action	= isset($existing->attributes['_Action']) ? $existing->attributes['_Action'] : null;
				// the xpath of the included page to embed (optional)
				$Use	= isset($existing->attributes['_Use']) ? $existing->attributes['_Use'] : null;
				$Args[]	= $existing->nodeName;
				trigger_error( 'Replacing ' .$existing->nodeName .' with ' .$Page .'/' .$Action, E_USER_WARNING );

				// if there's no _Use xpath, include everything inside the correct language section
				// if we include the entire /Page, we'll get infinite recursion in xsl
				// actions can return pages, so deal with xpath and Use here
				if( !$Use ){
					$Use = '/Page/Section[ lang(\'' .GXPage::get('Params')->getLanguage() .'\') ]/*';
				}
				$xpath = (substr($Use,0,1) == '/') ? $Use : '/Page/Section[ lang(\'' .GXPage::get('Params')->getLanguage() .'\') ]/' .$Use;
				// if no _Use, return the first result
				$numresults = ($Use) ? null : 0;

				// call method
				$result = GXPage::get('SiteConfig')->loadItem( $Page, $Action );
				// if it's a Page, run load
				if( get_class($result) == 'GXPage_Page' ){
					$result->load();
				}
				// run the xpath on the result
				$result = $result->getResult();
				if( !$result ){
					trigger_error( 'No results for ' .$Page .'/' .$Action, E_USER_WARNING );
					continue;
				}
				$in = $result->exec->xpath($xpath,$numresults);
				if( !$in ){
					trigger_error( 'Trying to include content, but xpath of included page did not return any results (' .$xpath .'), reverting to the raw xml result', E_USER_WARNING );
					$in = $result;
				}
				// if we have only one result, just use it
				if( is_array($in) && count($in) == 1 ){
					$in = $in[0];
				}

				// we might have an array from our xpath
				if( is_array($in) ){
					if( !is_object($in[0]) ){
						trigger_error( 'Unable to include results from ' .$Page .' / ' .$Action .' (first item is not an object)', E_USER_WARNING );
						continue;
					}
				}else{
					if( !is_object($in) && !is_string($in) ){
						trigger_error( 'Unable to include results from ' .$Page .' / ' .$Action .' (result is not an object or string)', E_USER_WARNING );
						continue;
					}
				}

				// perform the replacement
				if( is_array($in) ){
					for( $i = 0; $i < count($in); $i++ ){
						if( $i == 0 ){
							// set existing to existing's parent, since the first iteration will replace it and subsequent iterations will have nothing to attach to
							// find the parent and wrap it (see GXDOM::__constructor)
							$to = null;
							$parent = new GXDOM( $existing->doc, $existing->parentNode, null, null, $to, false, true );
							// parent is now a full GXDOM node belonging to existing's doc
							$existing->hit( $in[$i], GXDOM::NO_CONTAINER, GXDOM::REPLACE_CALLER );
							// set existing to parent
							$existing = $parent;
							continue;
						}
						$existing->{'gxpage-include'}( $in[$i], GXDOM::NO_CONTAINER );
					}
				}else{
					$existing->{'gxpage-include'}( $in, GXDOM::NO_CONTAINER, GXDOM::REPLACE_CALLER );
				}
			}
		}

		// handle @href replacement
		if( $links = $xml->exec->xpath('//*[ @href ]') ){
			// determine the value of Self ... if we're mod_rewrite, there should be no Self
			$self = isset($_SERVER['GXPAGE_MOD_REWRITE']) ? null : GXPage::get('Layout')->Client->Self;
			// loop through each hit, creating a hard-coded link
			foreach( $links as $a ){
				$str = $a->__attributes('href');
				// don't change anything if we have a double slash or it begins with "javascript:" or "mailto:"
				if( substr($str,0,1) == '#' || strpos($str,'//') || strpos($str,'javascript:') > -1 || substr($str,0,1) == '/' || strpos($str,'mailto:') > -1 || strpos($str,'tel:') > -1 ){
					trigger_error( 'We have @href, but it appears to be a hash, absolute link (double slash) or start with a protocol, so leaving it alone [' .$str .']', E_USER_WARNING );
					continue;
				}
				// prepend the link with absolute prefix, but only if we don't have gxpage-params
				$link = (isset($_SERVER['GXPAGE_MOD_REWRITE']) ? GXPage::get('Layout')->Client->Root : null) .$self .$str;
				$a->__attributes('href', $link);
				trigger_error('Replaced ' .$str .' with ' .$link, E_USER_WARNING);
			}
		}

		// check for @Use, the final xpath to be run when everything is loaded
		$use = $this->getConfig()->exec->xpath('File/@Use',0);
		if( $use ){
			// create a new GXDOM object using the node, or the document's root node will be /Page/Section instead of that requested by @Use
			// convert to xml text so its root node will become the document node in GXDOM
			$this->result = new GXDOM( $xml->exec->xpath($use->nodeValue,0)->exec->xml() );
		}else{
			$this->result = $xml;
		}
		trigger_error( 'Successfully loaded ' .$file, E_USER_WARNING );
		return true;
	}

	/*
	 * @xpath: the xpath to the node in this page's xml to replace
	 * @content: the stuff with which to replace the node
	 */
	public function setContent( $xpath = null, $content = null ){
		if( !$this->getPage() ){
			trigger_error( 'The page XML must be loaded before embedding content', E_USER_ERROR );
			return false;
		}

		// if xpath starts with a slash, just use it.
		// otherwise, assume we're replacing a node within the selected language.
		if( substr($xpath,0,1) == '/' ){
			$replaceXpath = $xpath;
		}else{
			$replaceXpath = '/Page/Section[ lang(\'' .GXPage::get('Params')->getLanguage() .'\') ]/' .$xpath;
		}

		// verify xpath on the loaded page
		if( !($existing = $this->getPage()->exec->xpath($replaceXpath,0)) ){
			trigger_error( 'Provided xpath returned no results (' .$replaceXpath .')', E_USER_ERROR );
			//return false;
		}

		// perform the replacement
		return $existing->{'gxpage-setcontent'}( $content, GXDOM::NO_CONTAINER, GXDOM::REPLACE_CALLER );
	}

	/**
	 * getTransform
	 *
	 * Returns boolean true if the page is supposed to be transformed.
	 * Returns boolean false if it's not supposed to be transformed.
	 * If the page has no Transform attribute, it returns the getTransform method from SiteConfig.
	 * @return boolean
	 */
	public function getTransform(){
		return (is_object($this->Transform) ? $this->Transform->nodeValue : GXPage::get('SiteConfig')->getTransform() );
	}

	/**
	 * getOutput
	 *
	 * Returns the Output value for this page.
	 * The constructor set it to null if there isn't one.
	 * @return mixed
	 */
	public function getOutput(){
		$out = $this->xpath('Output',0);
		return ( is_object($out) ? $out->nodeValue : GXPage::get('SiteConfig')->getOutput() );
	}

	/**
	 * getRuntime
	 *
	 * Returns boolean true if the page is supposed to include gxpage-runtime.
	 * Returns boolean false if it's not supposed to include gxpage-runtime.
	 * If the page has no Runtime attribute, it returns the getRuntime method from SiteConfig.
	 * @return boolean
	 */
	public function getRuntime(){
		return ( is_object($this->Runtime) ? $this->Runtime->nodeValue : GXPage::get('SiteConfig')->getRuntime() );
	}

}

?>